package com.apes.framework.plugin.scheduler;

import com.apes.framework.api.DomainService;
import com.apes.framework.jpa.lock.IsTryAgain;
import com.apes.framework.plugin.esb.api.EsbRouter;
import com.apes.framework.plugin.esb.api.SimpleRequest;
import com.apes.framework.util.*;
import lombok.Data;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.Lock;

/**
 * 功能：任务执行器
 *
 * @author xul
 * @create 2018-03-17 16:29
 */
@Service
public class TaskScheduler extends DomainService implements DisposableBean {
    private static final Map<String, Runner> runners = new HashMap();
    private Runner guardian;
    private boolean active = false;
    private boolean enabled;

    private ThreadPoolTaskScheduler threadPoolTaskScheduler;
    private TransactionTemplate transactionTemplate;

    @Autowired
    private TaskService taskService;

    @Autowired
    private LockUtil lockUtil;

    public TaskScheduler(ThreadPoolTaskScheduler threadPoolTaskScheduler, Environment env, TransactionTemplate transactionTemplate) {
        this.threadPoolTaskScheduler = threadPoolTaskScheduler;
        this.transactionTemplate = transactionTemplate;

        /*
         * 检查环境变量是否配置了主节点，如果是非主节点则不执行调度任务
         */
        String master;
        if (System.getenv("scheduler.master") != null) {
            master = System.getenv("scheduler.master");
        } else {
            master = env.getProperty("scheduler.master");
        }
        master = Tools.isNull(master) ? "false" : master;
        enabled = Boolean.valueOf(master);

        if (!enabled) return;

        /**主节点停机后，可以快速接管**/
        if (guardian == null) {
            guardian = new Runner("scheduler.cluster.retry", "0 */1 * * * *", "scheduler.cluster.retry", null);
            guardian.start();
        }
    }

    /*
     * 功能：启动定时任务
     * 注意：1、在集群环境中，参数："scheduler.master" 可以都设置为 "true"，采用选举机制;
     *      2、在非集群环境中，只有一台机器参数："scheduler.master"可以设置为"true";
     * 否则消息可能会被重复消费
     */
    public void start() {
        if (!enabled) return;

        if (!isMaster()) {
            if (active) cancel();
            return;
        }

        if (active) return;

        init();
        active = true;
    }

    private boolean isMaster() {
        return true;
    }

    public Map restart(SimpleRequest request) {
        if (!isMaster()) return MapUtil.mapper("message", "非主节点，不允许重启！");
        runners.forEach((k, runner) -> runner.restart());
        return MapUtil.mapper("message", "重启成功！");
    }

    public Map getServerInfo(SimpleRequest request) {
        return null;
    }

    public Map stop(SimpleRequest request) {
        if (!isMaster()) return MapUtil.mapper("message", "非主节点，不允许取消任务！");
        cancel();
        return MapUtil.mapper("message", "任务取消成功！");
    }

    /**
     * 功能：主服务shutdown后，可以快速接管
     */
    public void retry(SimpleRequest request) {
        start();
    }

    /**
     * 功能：设置初始化任务
     */
    private void init() {
        /**
         * 获取任务列表
         */
        invoke("schedule.task.run", null);
    }

    /**
     * 功能：取消所有任务
     */
    public void cancel() {
        runners.forEach((k, runner) -> runner.stop());
        active = false;
    }


    public void shutdown() {
        cancel();
        if (guardian != null) guardian.stop();
        threadPoolTaskScheduler.shutdown();
    }

    @Override
    public void destroy() {
        shutdown();
    }

    public void setTask(Task task) {
        Runner runner = runners.get(task.getId());
        if (runner == null) {
            runner = new Runner(task.getId());
            runners.put(task.getId(), runner);
        }
        runner.setCronExpression(task.getCronExpression());
        runner.setAction(new Action(runner.getId(), task.getMethod(), task.getParameter()));
        if (task.isValid()) {
            runner.restart();
        } else {
            runner.stop();
        }
    }

    /**
     * 功能：任务执行类
     */
    @Data
    public class Runner {
        /**
         * 任务ID
         */
        private String id;

        /**
         * cron表达式
         */
        private String cronExpression;

        private Action action;

        /**
         * 执行器
         */
        private ScheduledFuture scheduledFuture;

        public Runner(String id, String cronExpression, String method, String parameter) {
            this.id = id;
            this.cronExpression = cronExpression;
            this.action = new Action(id, method, parameter);
        }

        public Runner(String id) {
            this.id = id;
        }

        public void start() {
            if (scheduledFuture != null && !scheduledFuture.isCancelled()) return;
            scheduledFuture = threadPoolTaskScheduler.schedule(() -> action.process(), new CronTrigger(cronExpression));
        }

        public void restart() {
            stop();
            start();
        }

        public void stop() {
            if (scheduledFuture == null || scheduledFuture.isCancelled()) return;
            scheduledFuture.cancel(true);
            scheduledFuture = null;
        }
    }

    /**
     * 功能：动作类
     *
     * @author xul
     * @create 2018-03-17 18:04
     */
    @Data
    public class Action {
        protected final Logger logger = LogManager.getLogger(Action.class);

        private String id;
        private String method;
        private String parameter;

        public Action(String id, String method, String parameter) {
            this.id = id;
            this.method = method;
            this.parameter = parameter;
        }

        public void process() {
            Lock lock = lockUtil.getLocalLock("scheduler_" + id);
            if (lock.tryLock()) {
                try {
                    doProcess();
                } finally {
                    lock.unlock(); //释放锁
                }
            } else {
                taskService.saveLog(id, "有任务在执行中，该任务被丢弃！", true, new Date());
            }
        }

        private void doProcess() {
            if (method == null) return;
            TaskLog log = taskService.saveLog(id, "任务执行中！", true, new Date());
            try {
                boolean isTransaction = true; /**默认开启事务**/
                if (!Tools.isNull(EsbRouter.getRouter(method).getAttribute("isTransaction"))) {
                    isTransaction = "true".equals(EsbRouter.getRouter(this.method).getAttribute("isTransaction").toString().toLowerCase());
                }
                if (isTransaction) {
                    call(method, parameter);
                } else {
                    invoke(method, parameter);
                }
            } catch (Exception e) {
                logger.error("任务" + id + " 执行失败！" + e);
                if (!guardian.getAction().getId().equals(id)) {
                    log.setSuccess(false);
                    log.setDescription(e.getMessage());
                    taskService.saveLog(log);
                }
                return;
            }
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("%s : 任务 %s 执行成功", DateUtil.format(new Date(), DateUtil.SHOW_DATETIME_FORMAT), id));
            }
            if (!guardian.getAction().getId().equals(id)) {
                log.setSuccess(true);
                log.setDescription("任务执行成功");
                taskService.saveLog(log);
            }
        }
    }

    @IsTryAgain
    public void call(String method, String data) {
        transactionTemplate.execute(status -> {
            try {
                return invoke(method, data);
            } catch (Exception e) {
                status.isRollbackOnly();
                throw e;
            }
        });
    }
}
